package com.chixing.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.chixing.entity.Hotel;
import com.chixing.entity.OrderDetail;
import com.chixing.entity.RoomInventory;
import com.chixing.entity.Roomtype;
import com.chixing.entity.VO.RoomCountVO;
import com.chixing.mapper.HotelMapper;
import com.chixing.mapper.OrderDetailMapper;
import com.chixing.mapper.RoomInventoryMapper;
import com.chixing.mapper.RoomtypeMapper;
import com.chixing.service.IRoomInventoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chixing.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Array;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author smith
 * @since 2025-09-30
 */
@Service
public class RoomInventoryServiceImpl implements IRoomInventoryService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RoomInventoryMapper roomInventoryMapper;
    @Autowired
    private HotelMapper hotelMapper;
    @Autowired
    private RoomtypeMapper roomtypeMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;


    private static final int FUTURE_DAYS = 90; //要生成未来多少天的库存
    private static final int BATCH_SIZE = 500; // 批量插入优化(防止一次性sql太大)
    private static final int DEFAULT_TOTAL_COUNT = 10;//每个房型默认库存数量

    //初始化2个lua脚本
    private DefaultRedisScript<Long> decrCountScript; //减库存脚本
    private DefaultRedisScript<Long> delLockScript; //删除锁脚本

    //初始化的时候将这两个脚本加载进来
    public RoomInventoryServiceImpl(){
        //加载lua脚本
        decrCountScript =  new DefaultRedisScript<Long>();
        decrCountScript.setResultType(Long.class);
        decrCountScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/redis_decr_kucun.lua")));

        delLockScript =  new DefaultRedisScript<Long>();
        delLockScript.setResultType(Long.class);
        delLockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/redis_del_lock.lua")));
    }


    @Override
    public Result getRoomCount(Long hotelId, Date checkIn,Date checkOut) {
        List<RoomCountVO> roomInventories = roomInventoryMapper.selectHotelDateRoomTypeCount(hotelId, checkIn, checkOut);
        for (RoomCountVO r:roomInventories) {
            //将该时间段的某个房型的库存数量放一份在redis中
            Long roomTypeId = r.getRoomtypeId();
            Long count = r.getMinAvailable();
            String roomCountKey = "room_count_"+hotelId+"_"+ roomTypeId+"_"+checkIn.getTime() + "_" + checkOut.getTime();
            redisTemplate.opsForValue().setIfAbsent(roomCountKey,count);
        }
        if (roomInventories!=null && roomInventories.size()>0){
            return Result.getSuccess(roomInventories);
        }
        return Result.getFail(null);
    }

    @Override
    public Result decrCount(Long hotelId, Date checkIn, Date checkOut, Long roomTypeId) {


        String lockKey = "room_lock_" + hotelId + "_" + roomTypeId + "_" + checkIn.getTime() + "_" + checkOut.getTime();
        String lockValue = UUID.randomUUID().toString().replace("_", "");
        String roomCountKey = "room_count_" + hotelId + "_" + roomTypeId + "_" + checkIn.getTime() + "_" + checkOut.getTime();

        // 加锁（防止多个线程同时扣减）
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
        if (!isLock) {
            return Result.getFail("当前库存正在被其他线程操作");
        }

        try {
            Integer count = (Integer) redisTemplate.opsForValue().get(roomCountKey);
            if (count == null || count <= 0) {
                return Result.getFail("库存不足");
            }

            //Redis 减库存（原子操作）
            Long result = (Long) redisTemplate.execute(decrCountScript, Arrays.asList(roomCountKey), 1);
            if (result == null || result != 1) {
                return Result.getFail("Redis 扣减失败");
            }
            System.out.println("Redis 扣减成功");

            //数据库 locked_count + 1 （锁定房间）
            int rows = roomInventoryMapper.increaseLockedCount(hotelId, roomTypeId, checkIn,checkIn,checkOut);
            long days = ChronoUnit.DAYS.between(checkIn.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
                    checkOut.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
            if (rows == days) {
                System.out.println("数据库锁定库存成功");
                return Result.getSuccess("锁定成功");
            } else {
                // 回滚 Redis 库存（如果 DB 操作失败）
                redisTemplate.opsForValue().increment(roomCountKey);
                return Result.getFail("数据库锁定失败，已回滚 Redis");
            }

        } catch (Exception e) {
            e.printStackTrace();
            return Result.getFail("系统异常");
        } finally {
            // 释放锁
            Long rows = (Long) redisTemplate.execute(this.delLockScript, Arrays.asList(lockKey), lockValue);
            if (rows != null && rows > 0) {
                System.out.println("释放锁成功");
            } else {
                System.out.println("释放锁失败");
            }
        }
    }



    @Override
    public Result decrLockCountAddBookedCount(Long hotelId , LocalDate checkIn, LocalDate checkOut, Long roomTypeId) {
        int rows = roomInventoryMapper.addBookCount(hotelId, roomTypeId, checkIn, checkOut);
        if (rows>0)
            return Result.updateSuccess(rows);
        return Result.updateFail(null);
    }

    @Override
    @Transactional //启用事务管理，要么全成功要么全失败
    public void generateInventory() {
        //查询所有酒店
        List<Hotel> hotels = hotelMapper.selectList(null);
        //查询所有房型
        List<Roomtype> roomtypes = roomtypeMapper.selectList(null);
        //查询库存表中当前的最大日期
        LocalDate maxInvDate = roomInventoryMapper.findMaxInvDate();
        //如果还没有库存数据（第一次运行），就从今天开始生成，生成90天的
        //否则就从已有库存的最大日期的下一天开始生成，就只添加一天的
        LocalDate startDate = (maxInvDate ==null) ? LocalDate.now() : maxInvDate.plusDays(1);
        //存放要插入库存表的记录
        ArrayList<RoomInventory> insertList  = new ArrayList<>();
        //从start开始生成未来FUTURE_DAYS 天的库存
        for (int i = 0; i < FUTURE_DAYS ; i++) {
            //当前要生成的库存日期
            LocalDate localDate = startDate.plusDays(i);
            //遍历所有酒店
            for (Hotel hotel : hotels) {
                //遍历所有房型
                for (Roomtype roomtype:roomtypes) {
                    //创建库存对象
                    RoomInventory roomInventory = new RoomInventory();
                    //设置酒店id
                    roomInventory.setHotelId(hotel.getHotelId());
                    //设置房型id
                    roomInventory.setRoomtypeId(roomtype.getRoomtypeId());
                    //设置库存日期
                    roomInventory.setInvDate(localDate);
                    //设置默认总库存数
                    roomInventory.setTotalCount(DEFAULT_TOTAL_COUNT);
                    //已预定数初始为0
                    roomInventory.setBookedCount(0);
                    //已锁定数初始为0
                    roomInventory.setLockedCount(0);
                    //添加到库存列表
                    insertList.add(roomInventory);
                    //累计一定数量就插入一次到数据库
                    if (insertList.size()>=BATCH_SIZE){
                        roomInventoryMapper.insertBatchSomeColumn(insertList);
                        insertList.clear();
                    }
                }
            }
        }
        if (!insertList.isEmpty()) {
            roomInventoryMapper.insertBatchSomeColumn(insertList);
        }
    }

    //归档
    @Override
    @Transactional //"插入历史表”和“删除主表”要么都成功，要么都回滚。
    public void archiveInventoryMonthly() {
        // 1. 先插入到历史表
        roomInventoryMapper.archiveOldInventory();
        // 2. 再删除主表中的旧数据
        roomInventoryMapper.deleteArchivedInventory();
    }

    /**
     * 延迟退房的库存查询
     * @param hotelId 酒店id
     * @param roomTypeId 房型id
     * @param checkOut 哪天延迟退房
     * @return 库存剩余数量
     */
    @Override
    public Result getRoomTypeRoomCount(Long hotelId, Long roomTypeId, LocalDate checkOut) {
        //查的时候要西安看redis中是否存在如果存在就从redis中拿，如果不存在就去数据库查找
        Integer availableCount = null;
        String key = "delayRoomCount"+"_"+hotelId+"_"+roomTypeId+"_"+checkOut;
        if (redisTemplate.hasKey(key)){//如果redis中有这个key，就从redis中拿
            System.out.println("redis中存在这个房型的剩余库存数，正在从redis中取出数据---------");
            availableCount = (Integer) redisTemplate.opsForValue().get(key);
        }else {//如果不存在就去数据库中拿出来并且放一份到redis，并设置过期时间30min
            System.out.println("redis中不存在这个房型的剩余库存数，正在从MYSQL中取出数据---------");
            availableCount = roomInventoryMapper.selectRoomTypeRoomCount(hotelId, roomTypeId, checkOut);
            //加上随机过期时间，防止缓存雪崩
            redisTemplate.opsForValue().set(key,availableCount,30+new Random().nextInt(5),TimeUnit.MINUTES);
        }
        return Result.getSuccess(availableCount);
    }

    @Override
    public void releaseBookedInventory(Long orderId) {
        System.out.println("取消已支付订单，正在执行库存更新......");
        List<OrderDetail> details = orderDetailMapper.selectList(
                new QueryWrapper<OrderDetail>().eq("order_id", orderId)
        );
        for (OrderDetail orderDetail : details) {
            Long roomTypeId = orderDetail.getRoomtypeId();
            Integer roomCount = orderDetail.getRoomQuantity();
            LocalDate checkIn = orderDetail.getCheckIn();    // 确认字段名
            LocalDate checkOut = orderDetail.getCheckOut();

            //构建日期列表（入住日到退房前一天）
            List<LocalDate> dates = new ArrayList<>();
            for (LocalDate d = checkIn; d.isBefore(checkOut); d = d.plusDays(1)) {
                dates.add(d);
            }

            if (dates.isEmpty()) continue;

            //批量更新（一次SQL）
            int affected = roomInventoryMapper.releaseBookedInventoryBatch(roomTypeId, dates, roomCount);

            //检验affected行数，期望受影响 = dates.size()
            if (affected < dates.size()) {
                System.out.println("释放库存未完全成功，orderId=" + orderId
                        + " roomTypeId=" + roomTypeId + " expected=" + dates.size() + " affected=" + affected);
            }


            System.out.println("释放库存成功，orderId = " + orderId + "\t" +
                    "roomtypeId" + roomTypeId + "\t" +
                    "dates = " + dates + "\t" +
                    "roomCount = " + roomCount);
        }

    }
}
